/*
Copyright (c) 2007-2009, Yusuke Yamamoto
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the Yusuke Yamamoto nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY Yusuke Yamamoto ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL Yusuke Yamamoto BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package weibo4j;

import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import weibo4j.http.Response;
import weibo4j.org.json.JSONArray;
import weibo4j.org.json.JSONException;
import weibo4j.org.json.JSONObject;

/**
 * A data class representing Basic user information element
 */
public class User extends WeiboResponse implements java.io.Serializable {

    static final String[] POSSIBLE_ROOT_NAMES = new String[]{"user", "sender", "recipient", "retweeting_user"};
    private Weibo weibo;
    private long id;
    private String name;
    private String screenName;
    private String province;
    private String city;
    private String location;
    private String description;
    private String profileImageUrl;
    private String url;
    private String domain;
    private String gender;
    private int followersCount;
    private int friendsCount;
    private Date createdAt;
    private int favouritesCount;
    private boolean following;
    private int statusesCount;
    private boolean geoEnabled;
    private boolean allowAllActMsg;
    private boolean verified;
    private Status currentStatus;
    private static final long serialVersionUID = -6345893237975349030L;


    /*package*/User(Response res, Weibo weibo) throws WeiboException {
        super(res);
        Element elem = res.asDocument().getDocumentElement();
        init(elem, weibo);
    }

    /*package*/User(Response res, Element elem, Weibo weibo) throws WeiboException {
        super(res);
        init(elem, weibo);
    }
    /*package*/User(JSONObject json) throws WeiboException {
        super();
        init(json);
    }
    
    /*package*/User(Element elem) throws WeiboException {
        init(elem);
    }

    private void init(Element elem) throws WeiboException {
    	try {
			ensureRootNodeNameIs(POSSIBLE_ROOT_NAMES, elem);
			id = getChildInt("id", elem);
			name = getChildText("name", elem);
			screenName = getChildText("screen_name", elem);
			province = getChildText("province", elem);
			city = getChildText("city", elem);
			location = getChildText("location", elem);
			description = getChildText("description", elem);
			profileImageUrl = getChildText("profile_image_url", elem);
			url = getChildText("url", elem);
			domain = getChildText("domain", elem);
			gender = getChildText("gender", elem);
			followersCount = getChildInt("followers_count", elem);

			friendsCount = getChildInt("friends_count", elem);
			createdAt = getChildDate("created_at", elem);
			favouritesCount = getChildInt("favourites_count", elem);
			following = getChildBoolean("following", elem);
			statusesCount = getChildInt("statuses_count", elem);
			geoEnabled = getChildBoolean("geo_enabled", elem);
			verified = getChildBoolean("verified", elem);
			allowAllActMsg = getChildBoolean("allow_all_act_msg", elem);
			NodeList statuses = elem.getElementsByTagName("status");
			if (statuses.getLength() != 0) {
				Element status = (Element) statuses.item(0);
				currentStatus = new Status(status);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
    }
    private void init(JSONObject json) throws WeiboException {
        try {
            id = json.getInt("id");
            name = json.getString("name");
            screenName = json.getString("screen_name");
            province = json.getString("province");
            city = json.getString("city");
            location = json.getString("location");
            description = json.getString("description");
            profileImageUrl = json.getString("profile_image_url");
            url = json.getString("url");
            domain = json.getString("domain");
            gender = json.getString("gender");
            followersCount = json.getInt("followers_count");
            friendsCount = json.getInt("friends_count");
            createdAt = parseDate(json.getString("created_at"), "EEE MMM dd HH:mm:ss z yyyy");
            favouritesCount = json.getInt("favourites_count");
            following = getBoolean("following", json);
            statusesCount = json.getInt("statuses_count");
            verified = getBoolean("verified", json);
            geoEnabled = getBoolean("geo_enabled", json);
            allowAllActMsg = getBoolean("allow_all_act_msg", json);
            if (!json.isNull("status")) {
                JSONObject status = json.getJSONObject("status");
                currentStatus = new Status(status);
            }
        } catch (JSONException jsone) {
            throw new WeiboException(jsone.getMessage() + ":" + json.toString(), jsone);
        }
    }

    private void init(Element elem, Weibo weibo) throws WeiboException {
        this.weibo = weibo;
        ensureRootNodeNameIs(POSSIBLE_ROOT_NAMES, elem);
        id = getChildInt("id", elem);
        name = getChildText("name", elem);
        screenName = getChildText("screen_name", elem);
        province = getChildText("province", elem);
        city = getChildText("city", elem);
        location = getChildText("location", elem);
        description = getChildText("description", elem);
        profileImageUrl = getChildText("profile_image_url", elem);
        url = getChildText("url", elem);
        domain = getChildText("domain", elem);
        gender = getChildText("gender", elem);
        followersCount = getChildInt("followers_count", elem);

        friendsCount = getChildInt("friends_count", elem);
        createdAt = getChildDate("created_at", elem);
        favouritesCount = getChildInt("favourites_count", elem);
        following = getChildBoolean("following", elem);
        statusesCount = getChildInt("statuses_count", elem);
        geoEnabled = getChildBoolean("geo_enabled", elem);
        verified = getChildBoolean("verified", elem);
        allowAllActMsg = getChildBoolean("allow_all_act_msg", elem);
        NodeList statuses = elem.getElementsByTagName("status");
        //current_status = new Status()
        if (statuses.getLength() != 0) {
            Element status = (Element) statuses.item(0);
            currentStatus = new Status(status);
        }
    }

    /**
     * Returns the id of the user
     *
     * @return the id of the user
     */
    public long getId() {
        return id;
    }

    /**
     * Returns the name of the user
     *
     * @return the name of the user
     */
    public String getName() {
        return name;
    }

    /**
     * Returns the screen name of the user
     *
     * @return the screen name of the user
     */
    public String getScreenName() {
        return screenName;
    }

    /**
     * Returns the location of the user
     *
     * @return the location of the user
     */
    public String getLocation() {
        return location;
    }

    /**
     * Returns the description of the user
     *
     * @return the description of the user
     */
    public String getDescription() {
        return description;
    }

    /**
     * Returns the profile image url of the user
     *
     * @return the profile image url of the user
     */
    public URL getProfileImageURL() {
        try {
            return new URL(profileImageUrl);
        } catch (MalformedURLException ex) {
            return null;
        }
    }

    /**
     * Returns the url of the user
     *
     * @return the url of the user
     */
    public URL getURL() {
        try {
            return new URL(url);
        } catch (MalformedURLException ex) {
            return null;
        }
    }    

	public String getDomain() {
		return domain;
	}

	public String getGender() {
		return gender;
	}
	
	public Status getCurrentStatus(){
		return currentStatus;
	}


    /**
     * Returns the number of followers
     *
     * @return the number of followers
     * @since Weibo4J 1.0.4
     */
    public int getFollowersCount() {
        return followersCount;
    }

    public DirectMessage sendDirectMessage(String text) throws WeiboException {
        return weibo.sendDirectMessage(this.getName(), text);
    }

    public static List<User> constructUsers(Response res, Weibo weibo) throws WeiboException {
        Document doc = res.asDocument();
        if (isRootNodeNilClasses(doc)) {
            return new ArrayList<User>(0);
        } else {
            try {
                ensureRootNodeNameIs("users", doc);
//                NodeList list = doc.getDocumentElement().getElementsByTagName(
//                        "user");
//                int size = list.getLength();
//                List<User> users = new ArrayList<User>(size);
//                for (int i = 0; i < size; i++) {
//                    users.add(new User(res, (Element) list.item(i), weibo));
//                }
                
                //去除掉嵌套的bug
                NodeList list=doc.getDocumentElement().getChildNodes();
                List<User> users = new ArrayList<User>(list.getLength());
                Node node;
                for(int i=0;i<list.getLength();i++){
                	node=list.item(i);
                	if(node.getNodeName().equals("user")){
                		users.add(new User(res, (Element) node, weibo));
                	}
                }

                return users;
            } catch (WeiboException te) {
                if (isRootNodeNilClasses(doc)) {
                    return new ArrayList<User>(0);
                } else {
                    throw te;
                }
            }
        }
    }

    public static UserWapper constructWapperUsers(Response res, Weibo weibo) throws WeiboException {
		Document doc = res.asDocument();
		if (isRootNodeNilClasses(doc)) {
			return new UserWapper(new ArrayList<User>(0), 0, 0);
		} else {
			try {
				ensureRootNodeNameIs("users_list", doc);
				Element root = doc.getDocumentElement();
				NodeList user = root.getElementsByTagName("users");
				int length = user.getLength();
				if (length == 0) {
					return new UserWapper(new ArrayList<User>(0), 0, 0);
				}
				// 
				Element listsRoot = (Element) user.item(0);
                NodeList list=listsRoot.getChildNodes();
                List<User> users = new ArrayList<User>(list.getLength());
                Node node;
                for(int i=0;i<list.getLength();i++){
                	node=list.item(i);
                	if(node.getNodeName().equals("user")){
                		users.add(new User(res, (Element) node, weibo));
                	}
                }
				//
				long previousCursor = getChildLong("previous_curosr", root);
				long nextCursor = getChildLong("next_curosr", root);
				if (nextCursor == -1) { // 兼容不同标签名称
					nextCursor = getChildLong("nextCurosr", root);
				}
				return new UserWapper(users, previousCursor, nextCursor);
			} catch (WeiboException te) {
				if (isRootNodeNilClasses(doc)) {
					return new UserWapper(new ArrayList<User>(0), 0, 0);
				} else {
					throw te;
				}
			}
		}
	}

    public static List<User> constructUsers(Response res) throws WeiboException {
    	try {
            JSONArray list = res.asJSONArray();
            int size = list.length();
            List<User> users = new ArrayList<User>(size);
            for (int i = 0; i < size; i++) {
                users.add(new User(list.getJSONObject(i)));
            }
            return users;
        } catch (JSONException jsone) {
            throw new WeiboException(jsone);
        } catch (WeiboException te) {
            throw te;
        }  
    }
    
    /**
     * 
     * @param res
     * @return
     * @throws WeiboException
     */
    public static UserWapper constructWapperUsers(Response res) throws WeiboException {
		JSONObject jsonUsers = res.asJSONObject(); //asJSONArray();
		try {
			JSONArray user = jsonUsers.getJSONArray("users");
			int size = user.length();
			List<User> users = new ArrayList<User>(size);
			for (int i = 0; i < size; i++) {
				users.add(new User(user.getJSONObject(i)));
			}
			long previousCursor = jsonUsers.getLong("previous_curosr");
			long nextCursor = jsonUsers.getLong("next_cursor");
			if (nextCursor == -1) { // 兼容不同标签名称
				nextCursor = jsonUsers.getLong("nextCursor");
			}
			return new UserWapper(users, previousCursor, nextCursor);
		} catch (JSONException jsone) {
			throw new WeiboException(jsone);
		}
    }
    
    /**
     * @param res 
     * @return 
     * @throws WeiboException
     */
    static List<User> constructResult(Response res) throws WeiboException {
    	JSONArray list = res.asJSONArray();
    		try {
	             int size = list.length();
	             List<User> users = new ArrayList<User>(size);
	             for (int i = 0; i < size; i++) {
	                 users.add(new User(list.getJSONObject(i)));
	             }
	             return users;
			} catch (JSONException e) {
			}
      return null;
    }

    public int getFriendsCount() {
        return friendsCount;
    }

    public Date getCreatedAt() {
        return createdAt;
    }

    public int getFavouritesCount() {
        return favouritesCount;
    }

    public boolean isFollowing() {
        return following;
    }

    public int getStatusesCount() {
        return statusesCount;
    }

    /**
     * @return the user is enabling geo location
     * @since Weibo4J 2.0.10
     */
    public boolean isGeoEnabled() {
        return geoEnabled;
    }

	public boolean isAllowAllActMsg() {
		return allowAllActMsg;
	}

	/**
     * @return returns true if the user is a verified celebrity
     * @since Weibo4J 2.0.10
     */
    public boolean isVerified() {
        return verified;
    }


    @Override
    public int hashCode() {
        return (int)id;
    }
    
    public boolean isStautsEmpty(){
    	return currentStatus == null;
    }

    @Override
    public boolean equals(Object obj) {
        if (null == obj) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        return obj instanceof User && ((User) obj).id == this.id;
    }

    @Override
    public String toString() {
        return "User{" +
                "weibo=" + weibo +
                ", id=" + id +
                ", name='" + name + '\'' +
                ", screenName='" + screenName + '\'' +
                ", province='" + province + '\''+
                ", city='" + city + '\'' +
                ", location='" + location + '\'' +
                ", description='" + description + '\'' +
                ", profileImageUrl='" + profileImageUrl + '\'' +
                ", url='" + url + '\'' +
                ", domain='" + domain + '\'' +
                ", gender='" + gender + '\'' +
                ", followersCount=" + followersCount +
                ", friendsCount=" + friendsCount +
                ", createdAt=" + createdAt +
                ", favouritesCount=" + favouritesCount +
                ", statusesCount=" + statusesCount +
                ", geoEnabled=" + geoEnabled +
                ", allowAllActMsg=" + allowAllActMsg +
                ", verified=" + verified +
                ", status='" + (isStautsEmpty() ? null : currentStatus.toString()) + '\'' +
                '}';
    }
    
    public String getUserInfoSQLString(String encoding) throws UnsupportedEncodingException{
    	return "insert into users set user_id=" + id +
        ", name='" + URLEncoder.encode(name, encoding) + '\'' +
        ", screen_name='" +  URLEncoder.encode(screenName, encoding) + '\'' +
        ", province='" + URLEncoder.encode(province, encoding) + '\''+
        ", city='" + URLEncoder.encode(city, encoding) + '\'' +
        ", location='" + URLEncoder.encode(location, encoding)+ '\'' +
        ", description='" + URLEncoder.encode(description, encoding) + '\'' +
        ", profile_image_url='" + URLEncoder.encode(profileImageUrl, encoding) + '\'' +
        ", url='" + URLEncoder.encode(url, encoding) + '\'' +
        ", domain='" + URLEncoder.encode(domain, encoding) + '\'' +
        ", gender='" + URLEncoder.encode(gender, encoding) + '\'' +
        ", followers_count=" + followersCount +
        ", friends_count=" + friendsCount +
        ", created_at='" + myFormat.format(createdAt) + '\'' +
        ", favourites_count=" + favouritesCount +
        ", following=" + (following == true ? 1 : 0) +
        ", statuses_count=" + statusesCount +
        ", geo_enabled=" + (geoEnabled  == true ? 1 : 0) +
        ", allow_all_act_msg=" + (allowAllActMsg == true ? 1 : 0) +
        ", verified=" + (verified  == true ? 1 : 0)  +
        ", status_id=" + currentStatus.getId();
    }
    
    public String getUserInfoSQLString(String table, String encoding) throws UnsupportedEncodingException{
    	return "insert into " + table + " set user_id=" + id +
        ", name='" + URLEncoder.encode(name, encoding) + '\'' +
        ", screen_name='" +  URLEncoder.encode(screenName, encoding) + '\'' +
        ", province='" + URLEncoder.encode(province, encoding) + '\''+
        ", city='" + URLEncoder.encode(city, encoding) + '\'' +
        ", location='" + URLEncoder.encode(location, encoding)+ '\'' +
        ", description='" + URLEncoder.encode(description, encoding) + '\'' +
        ", profile_image_url='" + URLEncoder.encode(profileImageUrl, encoding) + '\'' +
        ", url='" + URLEncoder.encode(url, encoding) + '\'' +
        ", domain='" + URLEncoder.encode(domain, encoding) + '\'' +
        ", gender='" + URLEncoder.encode(gender, encoding) + '\'' +
        ", followers_count=" + followersCount +
        ", friends_count=" + friendsCount +
        ", created_at='" + myFormat.format(createdAt) + '\'' +
        ", favourites_count=" + favouritesCount +
        ", following=" + (following == true ? 1 : 0) +
        ", statuses_count=" + statusesCount +
        ", geo_enabled=" + (geoEnabled  == true ? 1 : 0) +
        ", allow_all_act_msg=" + (allowAllActMsg == true ? 1 : 0) +
        ", verified=" + (verified  == true ? 1 : 0)  +
        ", status_id=" + currentStatus.getId();
    }
    
    public Map<String, String> getRecordMap(String encoding) throws UnsupportedEncodingException{
		Map<String, String> res = new HashMap<String, String>();
		res.put("user_id=", Long.toString(id));
		res.put("name", URLEncoder.encode(name, encoding));
        res.put("screen_name", URLEncoder.encode(screenName, encoding));
        res.put("province", URLEncoder.encode(province, encoding));
        res.put("city=", URLEncoder.encode(city, encoding));
        res.put("location", URLEncoder.encode(location, encoding));
        res.put("description", URLEncoder.encode(description, encoding));
        res.put("profile_image_url", URLEncoder.encode(profileImageUrl, encoding));
        res.put("url", URLEncoder.encode(url, encoding));
        res.put("domain", URLEncoder.encode(domain, encoding));
        res.put("gender", URLEncoder.encode(gender, encoding));
        res.put("followers_count", Integer.toString(followersCount));
        res.put("friends_count", Integer.toString(friendsCount));
        res.put("created_at", '\'' + myFormat.format(createdAt) + '\'');
        res.put("favourites_count", Integer.toString(favouritesCount));
        res.put("following", Integer.toString(following == true ? 1 : 0));
        res.put("statuses_count", Integer.toString(statusesCount));
        res.put("geo_enabled", Integer.toString(geoEnabled  == true ? 1 : 0));
        res.put("allow_all_act_msg", Integer.toString(allowAllActMsg == true ? 1 : 0));
        res.put("verified", Integer.toString(verified  == true ? 1 : 0));
        res.put("status_id", Long.toString(currentStatus.getId()));
        return res;
	}
    
    public static String getFieldString(){
    	return "( user_id, name, screen_name, province, city, location, description, profile_image_url, url, " +
    			"domain, gender, followers_count, friends_count, created_at, favourites_count, following, " +
    			"statuses_count, geo_enabled, allow_all_act_msg, verified, status_id )";
    }
    
    public static String getEmptyValueString(){
    	return "(?,?,?,?,?,?,?,?," +
    			"?,?,?,?,?,?,?,?," +
    			"?,?,?,?,?)";
    }
    
    public String[] getRecord(String encoding) throws UnsupportedEncodingException{
    	String[] res = new String[21];
    	res[0] = Long.toString(id);
    	res[1] = URLEncoder.encode(name, encoding);
    	res[2] = URLEncoder.encode(screenName, encoding);
    	res[3] = URLEncoder.encode(province, encoding);
    	res[4] = URLEncoder.encode(city, encoding);
    	res[5] = URLEncoder.encode(location, encoding);
    	res[6] = URLEncoder.encode(description, encoding);
    	res[7] = URLEncoder.encode(profileImageUrl, encoding);
    	res[8] = URLEncoder.encode(url, encoding);
    	res[9] = URLEncoder.encode(domain, encoding);
    	res[10] = URLEncoder.encode(gender, encoding);
    	res[11] = Integer.toString(followersCount);
    	res[12] = Integer.toString(friendsCount);
    	res[13] = myFormat.format(createdAt);
    	res[14] = Integer.toString(favouritesCount);
    	res[15] = Integer.toString(following == true ? 1 : 0);
    	res[16] = Integer.toString(statusesCount);
    	res[17] = Integer.toString(geoEnabled  == true ? 1 : 0);
    	res[18] = Integer.toString(allowAllActMsg == true ? 1 : 0);
    	res[19] = Integer.toString(verified  == true ? 1 : 0);
    	res[20] = Long.toString(currentStatus.getId());
		return res;
    }
}
